package com.zlmlive.sql.vo;

import java.util.List;

import org.apache.log4j.Logger;

import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;
import com.zlmlive.sql.SqlParser;
import com.zlmlive.sql.SqlVo;
import com.zlmlive.sql.condition.WhereCondition;
import com.zlmlive.sql.exception.SqlParseException;
import com.zlmlive.sql.vo.ValueVo.ValueType;

/**
 * <pre>
 * 支持更新语法：
 * 
 * UPDATE tbl_name
 *  SET col_name1=expr1 [, col_name2=expr2 ...]
 *  [WHERE where_definition]
 *  
 * 示例
 *  UPDATE t_user SET age = 30 WHERE name = '张三'
 *  UPDATE t_user SET age = age + 1 WHERE name = '张三'
 * </pre>
 * @author Min
 *
 */
public class UpdateVo implements SqlVo {

	private static Logger log = Logger.getLogger(UpdateVo.class);

	public enum ColumnUpdateType {
		NORMAL, ADD, MINUS
	}

	private List<ColumnUpdateVo> setColumns;

	private String table;

	private WhereCondition condition;

	public void setSetColumns(List<ColumnUpdateVo> setColumns) {
		this.setColumns = setColumns;
	}

	public void setTable(String table) {
		this.table = table;
	}

	public void setCondition(WhereCondition condition) {
		this.condition = condition;
	}

	@Override
	public String getTable() {
		return this.table;
	}

	@Override
	public void check() {
		if (null == table || 0 == table.length()) {
			throw new SqlParseException("update [table] is null");
		}

		if (null == setColumns || 0 == setColumns.size()) {
			throw new SqlParseException("update [set] is null");
		}
		if (null != condition) {
			condition.check();
		}
	}

	@Override
	public String toSQL() {
		StringBuilder builder = new StringBuilder();
		builder.append(SqlParser.UPDATE_MARK);
		builder.append(SqlParser.BLANK_MARK);
		builder.append(table);
		builder.append(SqlParser.BLANK_MARK);
		builder.append("SET");
		builder.append(SqlParser.BLANK_MARK);
		for (int i = 0, n = setColumns.size(); i < n; i++) {
			if (i > 0) {
				builder.append(",");
				builder.append(SqlParser.BLANK_MARK);
			}
			ColumnUpdateVo columnUpdateVo = setColumns.get(i);
			builder.append(columnUpdateVo.getName());
			builder.append(SqlParser.BLANK_MARK);
			builder.append("=");
			builder.append(SqlParser.BLANK_MARK);

			ValueVo valueVo = columnUpdateVo.getValueVo();
			if (ColumnUpdateType.NORMAL == columnUpdateVo.getType()) {
				if (ValueType.STRING == valueVo.getType()) {
					builder.append('\'');
					builder.append(valueVo.getSqlValue());
					builder.append('\'');
				} else {
					builder.append(valueVo.getSqlValue());
				}
			} else if (ColumnUpdateType.ADD == columnUpdateVo.getType()) {
				builder.append(columnUpdateVo.getName());
				builder.append(SqlParser.BLANK_MARK);
				builder.append("+");
				builder.append(SqlParser.BLANK_MARK);
				builder.append(valueVo.getSqlValue());
			} else {
				builder.append(columnUpdateVo.getName());
				builder.append(SqlParser.BLANK_MARK);
				builder.append("-");
				builder.append(SqlParser.BLANK_MARK);
				builder.append(valueVo.getSqlValue());
			}
		}
		if (null != condition) {
			builder.append(SqlParser.BLANK_MARK);
			builder.append(SqlParser.WHERE_MARK);
			builder.append(SqlParser.BLANK_MARK);
			condition.toSQL(builder);
		}
		return builder.toString();
	}

	public int update(DBCollection collection, WriteConcern writeConcern) {
		DBObject query = new BasicDBObject();
		DBObject update = new BasicDBObject();

		if (null != this.condition) {
			this.condition.setQuery(query, null);
		}

		boolean hasSet = false;
		boolean hasInc = false;
		DBObject setObject = new BasicDBObject();
		DBObject incObject = new BasicDBObject();
		for (int i = 0, n = setColumns.size(); i < n; i++) {
			ColumnUpdateVo columnUpdateVo = setColumns.get(i);
			ValueVo valueVo = columnUpdateVo.getValueVo();
			if (ColumnUpdateType.NORMAL == columnUpdateVo.getType()) {
				setObject.put(columnUpdateVo.getName(), valueVo.getValue());
				hasSet = true;
			} else if (ColumnUpdateType.ADD == columnUpdateVo.getType()) {
				// updateSetting.put("$inc", new BasicDBObject(columnUpdateVo.getName(), valueVo.getValue()));
				incObject.put(columnUpdateVo.getName(), valueVo.getValue());
				hasInc = true;
			} else {
				if (ValueType.INTEGER == valueVo.getType()) {
					int val = ((Integer) valueVo.getValue()).intValue();
					// updateSetting.put("$inc", new BasicDBObject(columnUpdateVo.getName(), -val));
					incObject.put(columnUpdateVo.getName(), -val);
				} else if (ValueType.DOUBLE == valueVo.getType()) {
					double val = ((Double) valueVo.getValue()).doubleValue();
					// updateSetting.put("$inc", new BasicDBObject(columnUpdateVo.getName(), -val));
					incObject.put(columnUpdateVo.getName(), -val);
				} else {
					throw new SqlParseException(
							"When updating a minus operation, invalid data type: " + valueVo.getType());
				}
				hasInc = true;
			}
		}

		if (hasSet) {
			update.put("$set", setObject);
		}
		if (hasInc) {
			update.put("$inc", incObject);
		}

		log(query, update);

		// upsert: 如果未找到记录是否插入
		// multi:是否更新多条

		// WriteResult result = collection.update(query, update, false, true, WriteConcern.ACKNOWLEDGED);
		// WriteResult result = collection.update(query, update, false, true, MongoComponent.getInstance().getDefaultWriteConcern());
		WriteResult result = collection.update(query, update, false, true, writeConcern);

		return result.getN();
	}

	private void log(DBObject query, DBObject update) {
		if (log.isInfoEnabled()) {
			
			StringBuffer sql = new StringBuffer();
			sql.append("db.");
			sql.append(table);
			sql.append(".update(");
			
			if (null != query) {
				sql.append(query.toString());
			}
			if (null != update) {
				sql.append(",");
				sql.append(update.toString());
			}
			sql.append(")");
			log.info(sql.toString());
		}
	}
}
